#include <cassert>
#include <cfloat>
#include <cmath>

#include <QTime>
#include <QDebug>

#include "collection.h"
#include "trianglemf.h"
#include "trapezoidmf.h"
#include "leftendmf.h"
#include "rightendmf.h"

/** Create a collection with a uniform shape. End MFs are placed on the
  * ends and triangles are placed in between.
 */
Collection::Collection(const int& size) :
    m_size(size),
    m_sets(new GraphicalMF*[m_size]),
    m_input(0),
    m_is_output(0)
{

    /* Make the leftmost set an end one. */
    if(m_size > 0) {
        m_sets[0] = new LeftEndMF;
        m_sets[0]->setName("First");
    }

    /* Make the rightmost set also an end membership function. */
    if(m_size >= 2) {
        m_sets[size - 1] = new RightEndMF;
        m_sets[size - 1]->setName("Last");
        m_sets[size - 1]->shiftShape(size - 1);
    }

    /* Make the remaining sets triangles */
    for (int i = 1; i < m_size - 1; ++i)
    {
        m_sets[i]= new TriangleMF();
        m_sets[i]->shiftShape(i);
        m_sets[i]->setName("Middle");
    }

    //m_input = (double)(random() / (RAND_MAX + 1.0) * (getMaxTerm() + 1 - getMinTerm()) + getMinTerm() );
//    QTime midnight(0, 0, 0);
//    qsrand(midnight.secsTo(QTime::currentTime()));
//
//    double max = getMaxTerm();
//    double min = getMinTerm();
//    double myrand = rand();
//    m_input = rand() / ( RAND_MAX * 1.0 ) *( getMaxTerm() - getMinTerm() ) + getMinTerm();

}

Collection::~Collection()
{
    this->clear();
}

void Collection::clear()
{
    /* Delete each of the membership functions, and set the pointers to null,
     * for safety.
     */
    for (int i = 0; i < m_size; ++i)
    {
        delete m_sets[i];
        m_sets[i] = 0;
    }

    /* Delete the pointer to the array */
    delete[] m_sets;
    m_sets = 0;

    m_size = 0;
    m_is_output = false;
}

void Collection::setPoint(const int& whichMF,
                          const int& whichPoint,
                          const double& newLocation)
{
    /* Check that it is within the pointer's defined ranges. */
    assert(whichMF >= 0 && whichMF < m_size);

    /* Simply follow the pointer path to use the setPoint function.
     */
    m_sets[whichMF]->setPoint(whichPoint, newLocation);
}

/** Find the set in this collection that has the name that is passed in.
 * If no set matches, return null.
 */
BasicMembershipFunction* Collection::findSet(const QString& name) const
{
   for(int i = 0; i < this->m_size; ++i)
       /*Checking .contains() is wrong. == might not be the best option, but
        * it does actually check if the two strings are the same. And so far,
        * it works as long as the strings don't have extra characters.
        */
        if(m_sets[i]->getName().simplified() == name.simplified())
             return m_sets[i];

    return 0;
}

/** Get the list of membership functions in this collection that have a
 * membership greater than 0.
 */
QVector<BasicMembershipFunction *>* Collection::findTrueMembership() const
{
    QVector<BasicMembershipFunction *> *trueSets;
    trueSets = new QVector<BasicMembershipFunction *>;

    for(int i = 0; i < this->m_size; ++i)
        if(m_sets[i]->getFuzzyValue() > 0)
            trueSets->push_back(m_sets[i]);

    return trueSets;
}


GraphicalMF* Collection::getSet(const int &index) const {

    /* Make sure that we are within our bounds */
    assert(index >= 0 && index < m_size);

    return m_sets[index];
}

void Collection::setMF(const int& whichMF, GraphicalMF* newMF)
{
    /* Ensure we are within our bounds */
    assert(whichMF >=0 && whichMF < m_size);

    /* Clean up the old membership function */
    delete m_sets[whichMF];

    /* Replace it with the new one */
    m_sets[whichMF] = newMF;
}

void Collection::setSize(const int &newSize) {

    /* Ensure the new size makes sense */
    assert(newSize > 0);

    /* To prevent memory leaks, delete the old array of MFs here.*/
    this->clear();

    m_size = newSize;

    m_sets = new GraphicalMF*[m_size];

    /* Forgot this was necessary, added it back in */
    for(int i = 0; i < m_size; ++i)
        m_sets[i] = 0;
}

void Collection::setRandomFuzzyValue()
{
    QTime midnight(0, 0, 0);
    qsrand(midnight.secsTo(QTime::currentTime()));

//    double max = getMaxTerm();
//    double min = getMinTerm();
//    double myrand = rand();
    m_input = rand() / ( RAND_MAX * 1.0 ) *( getMaxTerm() - getMinTerm() )
              + getMinTerm();

    //emit resetInputOneFuzzy();
}

/** This function passes through the entire list of membership functions and
 * returns the maximum value that appears in the entire fuzzy set.
 */
double Collection::getMaxTerm() const
{
    double maxTerm = -DBL_MAX;

    Iterator i(*this);

    while(i.hasNext())
    {
        i.next();
        if (i.getPoint().x() > maxTerm)
        {
            maxTerm = i.getPoint().x();
        }
    }
    return maxTerm;
}

/** This function passes through the entire list of membership functions and
 * returns the minimum value that appears in the entire fuzzy set.
 */
double Collection::getMinTerm() const
{
    double minTerm = DBL_MAX;

    Iterator i(*this);

    while(i.hasNext())
    {
        i.next();
        if (i.getPoint().x() < minTerm)
        {
            minTerm = i.getPoint().x();
        }
    }
    return minTerm;
}

/** This function calculates the overal centroid of the entire output collection
 * and the final result is stored in m_output_crisp_number.
 */
double Collection::crispNumber()
{
    /*calculateCrispValue and calculateFuzzyValue must be done elsewhere.*/
    double xBar = 0;
    double xMin = this->getMinTerm();
    double fuzzyTotal = 0;

    for (int i = 0; i < m_size; ++i)
    {
        xBar += (m_sets[i]->getCrispValue()-xMin) * m_sets[i]->getFuzzyValue();
        fuzzyTotal += m_sets[i]->getFuzzyValue();
    }
    return m_output = xMin + xBar / fuzzyTotal;
}

/** This function is used for input collections. The input to the fuzzy system
  * is passed to this function, which then initializes the fuzzy values of
  * each membership function in the collection.
  */
void Collection::setFuzzyValues(const double &crispInput)
{
    m_input = crispInput;
    for (int i = 0; i < m_size; ++i)
    {
        m_sets[i]->calculateProbability(m_input);
    }
}


/************************ Collection::Iterator *******************************/
Collection::Iterator::Iterator(const Collection &collection) :
        m_sets(collection.m_sets),
        m_s(0),
        m_p(-1),
        m_size(collection.m_size)
{
}

Collection::Iterator::Iterator(GraphicalMF** existingPointer,
                               const int& existingSize) :
        m_sets(existingPointer),
        m_s(0),
        m_p(-1),
        m_size(existingSize)
{
}

void Collection::Iterator::next() {
    assert(m_s < m_size && m_p < m_sets[m_s]->getSize());

    if (m_sets[m_s]->getSize() == m_p + 1)
    {
        m_s++;
        m_p = 0;
    }
    else {
        m_p++;
    }
}

bool Collection::Iterator::hasNext() const {

    if( m_s == (m_size-1) && m_p == m_sets[m_s]->getSize()-1)
    {
        return false;
    }
    else
        return true;
}
